package nl.windgazer.arachne.services.yql;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Arrays;

import org.apache.tapestry5.json.JSONArray;
import org.apache.tapestry5.json.JSONObject;
import org.joda.time.DateTime;
import org.w3c.dom.Document;

public class YQLConduitImp implements YQLConduit {

	private final static String UTF8 = "UTF-8";

	private final StringBuilder jsonAttribs = attributes("format","json","callback","");

	private final String publicURL;

	private final String oauthURL;

	private final static YQLConduitCacheImp<JSONObject> JSONcache = new YQLConduitCacheImp<JSONObject>(15);

	// CTOR

	public YQLConduitImp (final String publicURL, final String oauthURL) {
		this.publicURL = publicURL;
		this.oauthURL = oauthURL;
	}

	// Service Methods

	public Document domByQuery(final String yqlQuery, Object... args) {
		throw new RuntimeException("NYI");
	}

	public Document domByAlias(final String prefix, final String alias, Object... args) {
		throw new RuntimeException("NYI");
	}

	public JSONObject jsonByQuery(final String yqlQuery, Object... args) {
		try {
			final URL qURL = getQueryUrl(yqlQuery, true, jsonAttribs, args);
			return jsonByURL(qURL);
		} catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
	}

	public JSONObject jsonByAlias(final String prefix, final String alias, Object... args) {
		try {
			final URL qURL = getAliasUrl(prefix, alias, true, jsonAttribs, args);
			return jsonByURL(qURL);
		} catch (MalformedURLException e) {
			throw new RuntimeException(e);
		}
	}

	public YQLRSSEntry[] getRSSEntries(String... urls) {
		StringBuilder sb = new StringBuilder();
		for (final String url:urls) {
			if (sb.length() < 1) {
				sb.append(" WHERE ");
			} else {
				sb.append(" OR ");
			}
			sb.append(" url='")
			.append(url)
			.append('\'');
		}
		sb.insert(0, "SELECT title,link,description,pubDate FROM rss ");
		final JSONObject q = jsonByQuery(sb.toString());
		if (q==null) {
			System.out.println(sb.toString());
			return new YQLRSSEntry[]{};
		}
		final int count = q.getInt("count");
		YQLRSSEntry[] results = null;
		if (count>1) {
			final JSONArray items = q.getJSONObject("results").getJSONArray("item");
			results = new YQLRSSEntry[items.length()];
			for (int i = 0; i < results.length; i++) {
				results[i] = YQLRSSEntryFactory.getEntry(items.getJSONObject(i));
			}
		} else {
			results = new YQLRSSEntry[count];
			if (count == 1) {
				results[0] = YQLRSSEntryFactory.getEntry(q.getJSONObject("results").getJSONObject("item"));
			}
		}
		Arrays.sort(results);
		return results;
	}

	// Helper methods

	/**
	 * This method keeps a 15 minute cached result-set to reduce the need for external queries...
	 */
	private JSONObject jsonByURL(final URL qURL) {
		try {
			final String key = qURL.toString();
			YQLConduitCacheVO<JSONObject> cache = JSONcache.get(key);
			if (cache==null) {
				cache = new YQLConduitCacheVO<JSONObject>(key, new JSONObject(getURLContent(qURL).toString()).getJSONObject("query"), new DateTime());
				JSONcache.put(key, cache);
			}
			return cache.getResult();
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println(qURL);
		}
		return null;
	}

	public static StringBuilder getURLContent(URL url) throws IOException {
		URLConnection conn = url.openConnection();
		String encoding = conn.getContentEncoding();
		if (encoding == null) {
			encoding = "ISO-8859-1";
		}
		BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), encoding));
		StringBuilder sb = new StringBuilder(16384);
		try {
			String line;
			while ((line = br.readLine()) != null) {
				sb.append(line);
				sb.append('\n');
			}
		} finally {
			br.close();
		}
		return sb;
	}

    public static StringBuilder attributes(Object... namesAndValues)
    {
    	final StringBuilder sb = new StringBuilder();

    	int i = 0;

        while (i < namesAndValues.length)
        {

            String name = namesAndValues[i++].toString();
            Object value = namesAndValues[i++];

            if (value!=null) {
            	try {
            		final String v = URLEncoder.encode(value.toString(), UTF8);

            		if (i==2) {
                		//sb.append('?');
                	} else {
                		sb.append('&');
                	}

                	sb.append(name);
                	sb.append('=');
					sb.append(v);

            	} catch (UnsupportedEncodingException e) {
					throw new RuntimeException("UTF-8 not supported, cannot safely encode values, YQL queries cannot be created...");
				}
            }

        }
        
        return sb;
    }

    private URL getAliasUrl(final String prefix, final String alias, final boolean pubServer, final StringBuilder options, Object... args) throws MalformedURLException {
    	final StringBuilder sb = new StringBuilder();
    	final boolean hasOptions = options !=null && options.length() > 0;
    	//Check for public or oauth and append yql-url
    	if (pubServer) {
    		sb.append(publicURL);
    	} else {
    		sb.append(oauthURL);
    	}
    	//add prefix
    	sb.append('/');
    	sb.append(prefix);
    	//add alias
    	sb.append('/');
    	sb.append(alias);
    	//add ?
    	sb.append('?');
    	//add args
    	final StringBuilder argSb = attributes(args);
    	if (argSb.length() > 0) {
    		sb.append(argSb);
    		if (hasOptions) sb.append('&');
    	}
    	//add options
    	if (hasOptions) {
    		sb.append(options);
    	}

    	return new URL(sb.toString());
    }

    private URL getQueryUrl(final String yqlQuery, final boolean pubServer, final StringBuilder options, Object... args) throws MalformedURLException {
    	final StringBuilder sb = new StringBuilder();
    	final boolean hasOptions = options !=null && options.length() > 0;
    	final boolean hasArgs = args != null && args.length > 0;
    	//Check for public or oauth and append yql-url
    	if (pubServer) {
    		sb.append(publicURL);
    	} else {
    		sb.append(oauthURL);
    	}
    	//add ?
    	sb.append('?');
    	//add args
    	final StringBuilder argSb = attributes("q",yqlQuery);
		sb.append(argSb);
    	if (hasOptions) {
    		sb.append('&');
    		//add options
    		sb.append(options);
    	}
		if (hasArgs) {
			sb.append('&');
			//add args
			sb.append(attributes(args));
		}

    	return new URL(sb.toString());
    }

}
